/*
 * Copyright (c) 2010, Johan T. Larsson All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Mercenary Commander project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *      
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JOHAN T. LARSSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package games.TBSGame;

import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Timer;

import javax.microedition.khronos.opengles.GL10;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.Bundle;
import android.util.Log;
import android.util.Xml;

public class BattleWorld {
	static final int NORTH_EAST = 0;
	static final int EAST = 1;
	static final int SOUTH_EAST = 2;
	static final int SOUTH_WEST = 3;
	static final int WEST = 4;
	static final int NORTH_WEST = 5;
	
	GameEnder CurrentGame;
		
	public int Size;
	
	public ArrayList<Unit> Units;
	public ArrayList<Unit> VisibleUnits = new ArrayList<Unit>();
	Random Rng;
	
	ResourceManager ResourceManager;
	
	public Tile[][] Landscape;
	ArrayList<GoalTile> Goals = new ArrayList<GoalTile>();
	int TurnsLeft;
	
	String LevelFile;
	
	int Attacker = 0;
	int Defender = 1;
	int LocalTeam;
	
	public BattleWorld(GameEnder game, int size, int localTeam)
	{
		CurrentGame = game;
		LocalTeam = localTeam;
		ResourceManager = games.TBSGame.ResourceManager.Singleton;
		
		Size = size;
		
		Rng = new Random();
		
		Units = new ArrayList<Unit>();
		
		Landscape = new Tile[size][size];
		for (int i = 0; i < Landscape.length; i++) {
			for (int j = 0; j < Landscape[i].length; j++) {
				Landscape[i][j] = new Tile();
			}
		}
	}
	
	public BattleWorld(GameEnder game, String filename, int localTeam)
	{
		CurrentGame = game;
		LocalTeam = localTeam;
		LevelFile = filename;
		ResourceManager = games.TBSGame.ResourceManager.Singleton;
		
		Rng = new Random();
		
		Units = new ArrayList<Unit>();

		ParseLevelFile(filename, false);
		CalculateVisibility();
	}
	
	private final void ParseLevelMap(String filename)
	{
		String line;
        try {
        	AssetManager assMan = ResourceManager.GetAppAssets();
        	
        	BufferedReader file = new BufferedReader(new InputStreamReader(assMan.open(filename)));
        	int row = Size - 1;
       		while((line = file.readLine()) != null && row >= 0)
       		{
       			for(int col = 0; col < Size; ++col)
       			{
       				switch(line.charAt(col))
       				{
       				case 'W':
       					Landscape[col][row] = Tile.CreateWater();
       					break;
       				case 'G':
       					Landscape[col][row] = Tile.CreateGrass();
       					break;
       				case 'D':
       					Landscape[col][row] = Tile.CreateDesert();
       					break;
       				}
       			}
       			--row;
       		}
        	file.close();
        }
        catch (IOException ex){
        	ex.printStackTrace();
        }
        
        int[] heights = new int[6];
        Point p;
        for(int i = 0; i < Size; ++i)
        {
        	for(int j = 0; j < Size; ++j)
        	{
        		for(int k = 0; k < 6; ++k)
        		{
        			heights[k] = 10;
        			p = AdjacentCoordinates(k, i, j);
        			if(p != null)
        				heights[k] = Landscape[p.x][p.y].Height;
        		}
        		Landscape[i][j].InitCoords(heights);
        	}
        }
	}
	
	public final void ParseLevelFile(String filename, boolean staticsOnly)
	{
		try {
			XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
			XmlPullParser xpp = factory.newPullParser();
			AssetManager assMan = ResourceManager.GetAppAssets();
			InputStream iStream = null;
			try {
				iStream = assMan.open(filename);
			} catch (IOException e) {
				Log.e("ParseLevel", "Could not open file:" + filename);
			}		
			xpp.setInput(iStream, null);

			int eventType = xpp.getEventType();
			String tagName;
			while(eventType != XmlPullParser.END_DOCUMENT) {
				if(eventType == XmlPullParser.START_TAG) {
					tagName = xpp.getName();
					if(tagName.equals("level")) {
						ParseLevelTag(xpp, staticsOnly);
					}
					else if(tagName.equals("unit")) {
						ParseUnitTag(xpp, staticsOnly);
					}
					else if(tagName.equals("goal")) {
						ParseGoalTag(xpp, staticsOnly);
					}
				}
				try {
					eventType = xpp.next();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} catch (XmlPullParserException e) {
			e.printStackTrace();
		}
	}
	
	private void ParseGoalTag(XmlPullParser xpp, boolean staticsOnly) {
		GoalTile goal = new GoalTile();
		goal.X = Integer.parseInt(xpp.getAttributeValue(null, "posx"));
		goal.Y = Integer.parseInt(xpp.getAttributeValue(null, "posy"));
		String optional = xpp.getAttributeValue(null, "optional");
		if(optional != null && optional.equals("true"))
			goal.Optional = true;
		
		String owner = xpp.getAttributeValue(null, "owner");
		if(owner != null)
			goal.Owner = Integer.parseInt(owner);
		
		String id = xpp.getAttributeValue(null, "id");
		if(id == null)
		{
			Log.println(Log.ASSERT, "ParseGoalTag", "No goal ID found in xml");
			return;
		}
		goal.id = Integer.parseInt(id);
		
		Iterator<GoalTile> it = Goals.iterator();
		while(it.hasNext()) 
		{
			if(it.next().id == goal.id)
			{
				Log.println(Log.ASSERT, "ParseGoalTag", "Duplicate goal ID found in xml");
				return;
			}
		}
		
		Goals.add(goal);
	}

	private void ParseUnitTag(XmlPullParser xpp, boolean staticsOnly) {
		if(staticsOnly)
			return;
		
		Unit unit = null;
		String type = xpp.getAttributeValue(null, "type");
		int posX = Integer.parseInt(xpp.getAttributeValue(null, "posx"));
		int posY = Integer.parseInt(xpp.getAttributeValue(null, "posy"));
		int team = Integer.parseInt(xpp.getAttributeValue(null, "team"));
		if(type.equals("tank"))
			unit = Unit.CreateTank(posX, posY, team, this);
		else if(type.equals("soldier"))
			unit = Unit.CreateInfantry(posX, posY, team, this);
		else if(type.equals("artillery"))
			unit = Unit.CreateLauncher(posX, posY, team, this);
		
		if(unit != null)
			AddUnit(unit);
	}

	private void ParseLevelTag(XmlPullParser xpp, boolean staticsOnly) {
		int attribCount = xpp.getAttributeCount();
		for(int i = 0; i < attribCount; ++i) {
			String name = xpp.getAttributeName(i);
			
			if(name.equals("size")){
				Integer size = Integer.parseInt(xpp.getAttributeValue(i));
				Size = size;
				Landscape = new Tile[size][size];
				
			}
			else if(!staticsOnly && name.equals("timelimit")){
				TurnsLeft = Integer.parseInt(xpp.getAttributeValue(i));
			}
			else if(name.equals("mapfile")) {
				ParseLevelMap(xpp.getAttributeValue(i));
			}
			else if(name.equals("overlayfile")) {
				ParseLevelOverlay(xpp.getAttributeValue(i));
			}
		}
	}

	private void ParseLevelOverlay(String filename) {
		String line;
        try {
        	AssetManager assMan = ResourceManager.GetAppAssets();
        	
        	BufferedReader file = new BufferedReader(new InputStreamReader(assMan.open(filename)));
        	int row = Size - 1;
       		while((line = file.readLine()) != null && row >= 0)
       		{
       			for(int col = 0; col < Size; ++col)
       			{
       				switch(line.charAt(col))
       				{
       				case 'C':
       					Landscape[col][row].OverlayCity();
       					break;
       				case 'F':
       					Landscape[col][row].OverlayForest();
       					break;
       				}       					
       			}
       			--row;
       		}
        	file.close();
        }
        catch (IOException ex){
        	ex.printStackTrace();
        }
	}

	public final Point MapCoordinates(double x, double y)
	{
		Point point = new Point();
		point.y = (int)y;
		if(point.y % 2 == 0)
			point.x = (int)x;
		else
			point.x = (int)(x - 0.5);
		
		if(point.x < 0)
			point.x = 0;
		if(point.y < 0)
			point.y = 0;
		if(point.x >= Size)
			point.x = Size - 1;
		if(point.y >= Size)
			point.y = Size - 1;
		return point;
	}
	
	public final Point AdjacentCoordinates(int direction, int x, int y)
	{
		Point point = new Point();
		
		switch(direction)
		{
		case NORTH_EAST:
			point.y = y + 1;
			point.x = x;
			if(y % 2 != 0)
			{
				point.x++;
			}
			break;
		case EAST:
			point.x = x + 1;
			point.y = y;
			break;
		case SOUTH_EAST:
			point.y = y - 1;
			point.x = x;
			if(y % 2 != 0)
			{
				point.x++;
			}
			break;
		case SOUTH_WEST:
			point.y = y - 1;
			point.x = x;
			if(y % 2 == 0)
			{
				point.x--;
			}
			break;
		case WEST:
			point.x = x - 1;
			point.y = y;
			break;
		case NORTH_WEST:
			point.y = y + 1;
			point.x = x;
			if(y % 2 == 0)
			{
				point.x--;
			}
			break;
		}
		
		if(point.x >= Size)
			return null;
		else if(point.x < 0)
			return null;
		if(point.y >= Size)
			return null;
		else if(point.y < 0)
			return null;
		
		return point;
	}
	
	// TODO: Naive implementation until we have obstacles.
	public ArrayList<Point> FindPath(int fromX, int fromY, int toX, int toY)
	{
		ArrayList<Point> path = new ArrayList<Point>();
		Point nextStep = new Point(fromX, fromY);
		
		while(nextStep.x != toX || nextStep.y != toY)
		{
			if(nextStep.y == toY)
			{
				if(nextStep.x > toX)
					nextStep.x--;
				else
					nextStep.x++;
			}
			else
			{
				if(nextStep.x > toX && nextStep.y % 2 == 0)
					nextStep.x--;
				else if(nextStep.x < toX && nextStep.y % 2 != 0)
					nextStep.x++;
			
				if(nextStep.y > toY)
					nextStep.y--;
				else
					nextStep.y++;
			}
			path.add(new Point(nextStep));
		}
		return path;
	}
	
	//TODO: Much too slow. FIX!
	public int Distance(int fromX, int fromY, int toX, int toY)
	{
		int result = 0;
		Point nextStep = new Point(fromX, fromY);
		while(nextStep.x != toX || nextStep.y != toY)
		{
			if(nextStep.y == toY)
			{
				if(nextStep.x > toX)
					nextStep.x--;
				else
					nextStep.x++;
			}
			else
			{
				if(nextStep.x > toX && nextStep.y % 2 == 0)
					nextStep.x--;
				else if(nextStep.x < toX && nextStep.y % 2 != 0)
					nextStep.x++;
			
				if(nextStep.y > toY)
					nextStep.y--;
				else
					nextStep.y++;
			}
			++result;
		}
		return result;
	}
	
	public void AddUnit(Unit unit)
	{
		Units.add(unit);
		Landscape[unit.PosX][unit.PosY].Unit = unit;
	}
	
	public void DeleteUnit(Unit unit)
	{
		Units.remove(unit);
		Landscape[unit.PosX][unit.PosY].Unit = null;
	}
	
	public void DeleteUnit(int x, int y)
	{
		Units.remove(Landscape[x][y].Unit);
		Landscape[x][y].Unit = null;
	}
	
	public boolean MoveUnit(int fromX, int fromY, int toX, int toY)
	{
		if(Landscape[fromX][fromY].Unit == null || Landscape[toX][toY].Unit != null)
			return false;
		
		Landscape[toX][toY].Unit = Landscape[fromX][fromY].Unit;
		return true;
	}

	// TODO: Deal with stationary units acting as obstacles
	// TODO: Deal with impassable terrain
	public synchronized void DoTick(int ticks) {
		int unitAmount = Units.size();
		Unit unit;
		ArrayList<Unit> movingUnits = new ArrayList<Unit>();
		ArrayList<Unit> bombardingUnits = new ArrayList<Unit>();
		
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = Units.get(i);
			if(unit.State == Unit.MOVING)
			{
				movingUnits.add(unit);
				unit.OrderProgress = 0;
			}
			else if(unit.State == Unit.BOMBARDING)
			{
				bombardingUnits.add(unit);
				unit.OrderProgress = 0;
			}
		}
		
		Collections.sort(movingUnits);
		unitAmount = movingUnits.size();
		for(int tick = 0; tick < ticks; ++tick)
		{
			Iterator<Unit> it = bombardingUnits.iterator();
			while(it.hasNext())
			{
				TurnBombard(it.next());
			}
			
			for(int i = 0; i < unitAmount; ++i)
			{
				unit = movingUnits.get(i);
				
				++unit.TicksSinceLastMove;
				if(unit.State == Unit.MOVING && unit.TicksSinceLastMove >= unit.Slowness)
				{
					TurnMoveUnit(unit, unit.OrderPath.get(unit.OrderProgress));
				}
			}
			CalculateVisibility();
		}
		
		ArrayList<Point> tempOrders;
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = movingUnits.get(i);
			if(unit.State == Unit.MOVING)
			{
				tempOrders = unit.OrderPath;
				unit.OrderPath = new ArrayList<Point>();
				for(int j = unit.OrderProgress; j < tempOrders.size(); ++j)
				{
					unit.OrderPath.add(tempOrders.get(j));
				}
			}
			else if(unit.State != Unit.DEAD)
			{
				unit.State = Unit.GUARDING;
				unit.OrderPath.clear();
			}
		}
		Iterator<Unit> it = bombardingUnits.iterator();
		while(it.hasNext())
		{
			Unit bomber = it.next();
			if(bomber.State == Unit.OUT_OF_ORDERS)
			{
				bomber.State = Unit.GUARDING;
				bomber.OrderPath.clear();
			}
		}
	}
	
	private void CalculateVisibility()
	{		
		VisibleUnits.clear();
		for(int i = 0; i < Size; ++i)
		{
			for(int j = 0; j < Size; ++j)
			{
				Landscape[i][j].Visible = false;
			}
		}
		
		Iterator<Unit> it = Units.iterator();
		Unit unit;
		while(it.hasNext())
		{
			unit = it.next();
			if(unit.Team == LocalTeam)
			{
				for(int x = Math.max(unit.PosX - unit.VisionRange, 0); x < Math.min(unit.PosX + unit.VisionRange + 1, Size); ++x)
				{
					for(int y = Math.max(unit.PosY - unit.VisionRange, 0); y < Math.min(unit.PosY + unit.VisionRange + 1, Size); ++y)
					{
						if(Distance(x, y, unit.PosX, unit.PosY) <= unit.VisionRange)
						{
							Landscape[x][y].Visible = true;
							if(Landscape[x][y].Unit != null)
							{
								VisibleUnits.add(Landscape[x][y].Unit);
							}
						}
					}
				}
			}
		}
	}
	
	public void FinalizeTurn()
	{
		Iterator<GoalTile> iter = Goals.iterator();
		GoalTile goal;
		Unit unit;
		int goalsToWin = Goals.size();
		while(iter.hasNext())
		{
			goal = iter.next();
			unit = Landscape[goal.X][goal.Y].Unit;
			if(unit != null)
			{
				goal.Owner = unit.Team; 
			}
			if(goal.Optional || goal.Owner == Attacker)
			{
				--goalsToWin;
			}
		}
		
		if(goalsToWin == 0) {
			GameOver(Attacker);
		}
		else if(TurnsLeft <= 0) {
			GameOver(Defender);
		}
		--TurnsLeft;
	}
	
	private void GameOver(int winner) {
		Iterator<GoalTile> iter = Goals.iterator();
		GoalTile goal;
		int optionalGoals = 0;
		while(iter.hasNext())
		{
			goal = iter.next();
			if(goal.Optional && goal.Owner == winner)
			{
				++optionalGoals;
			}
		}
		
		CurrentGame.EndGame(winner, optionalGoals);
	}

	private void TurnBombard(Unit unit) {
		if(unit.State != Unit.BOMBARDING)
			return;
		
		Point target = unit.OrderPath.get(0);
		Unit otherUnit = Landscape[target.x][target.y].Unit;
		if(otherUnit != null && otherUnit.Team != unit.Team && Landscape[otherUnit.PosX][otherUnit.PosY].Visible)
		{
			unit.State = Unit.OUT_OF_ORDERS;
			
			int unitValue = unit.GetAttack() + Rng.nextInt(50) - 25;
			int otherValue = otherUnit.GetAttack() + Rng.nextInt(50) - 25;
			
			otherValue += Landscape[target.x][target.y].DefenseBonus;

			if(unitValue >= otherValue)
			{
				otherUnit.HealthPercentage -= unitValue - otherValue;
				if(otherUnit.HealthPercentage <= 0)
					otherUnit.HealthPercentage = 1;
			} 
			else if(otherUnit.Range >= Distance(unit.PosX, unit.PosY, otherUnit.PosX, otherUnit.PosY))
			{
				unit.HealthPercentage -= otherValue - unitValue;
				if(unit.HealthPercentage <= 0)
					unit.HealthPercentage = 1;
			}
		}
	}

	private boolean TurnUnitFlee(Unit unit) {
		Point point;
		ArrayList<Point> freePoints = new ArrayList<Point>();
		for(int i = 0; i < 6; ++i)
		{
			point = AdjacentCoordinates(i, unit.PosX, unit.PosY);
			if(point != null && Landscape[point.x][point.y].Unit == null && Landscape[point.x][point.y].Passable)
			{
				freePoints.add(new Point(point));
			}
		}
		if(freePoints.size() > 0)
		{
			point = freePoints.get(Rng.nextInt(freePoints.size()));
			
			Landscape[unit.PosX][unit.PosY].Unit = null;
			unit.PosX = point.x;
			unit.PosY = point.y;
			Landscape[point.x][point.y].Unit = unit;
			unit.OrderPath.clear();
			return true;
		}
		return false;
	}

	//TODO: Make better check to see if def or off is to be used.
	//TODO: Improve algorithm for fairness
	//TODO: Check for bugs
	private boolean TurnCombat(Unit unit, Unit otherUnit, Point location) {
		int unitValue = unit.GetAttack() + Rng.nextInt(50) - 25;
		int otherValue;
		if(otherUnit.State == Unit.GUARDING)
		{
			otherValue = otherUnit.GetDefense() + Rng.nextInt(50) - 25;
			otherValue += Landscape[location.x][location.y].DefenseBonus;
		}
		else
			otherValue = otherUnit.GetAttack() + Rng.nextInt(50) - 25;
		
		//Add firepower from guarding units within range
		Unit nearUnit;
		int unitAmount = Units.size();
		for(int i = 0; i < unitAmount; ++i)
		{
			nearUnit = Units.get(i);
			if(nearUnit != unit
				&& nearUnit != otherUnit
				&& nearUnit.State == Unit.GUARDING
				&& nearUnit.Range >= Distance(nearUnit.PosX, nearUnit.PosY, location.x, location.y))
			{
				if(nearUnit.Team == unit.Team)
				{
					unitValue += nearUnit.GetSupport();
				}
				else if(nearUnit.Team == otherUnit.Team)
				{
					otherValue += nearUnit.GetSupport();
				}
			}
		}
		
		if(unitValue >= otherValue)
		{
			otherUnit.HealthPercentage -= unitValue - otherValue;
			return true;
		}
		else
		{
			unit.HealthPercentage -= otherValue - unitValue;
			return false;
		}
	}

	private boolean TurnPlaceUnit(Unit unit, Point to)
	{
		if(Landscape[to.x][to.y].Unit == null)
		{
			unit.TicksSinceLastMove = 0;
			++unit.OrderProgress;
			Landscape[unit.PosX][unit.PosY].Unit = null;
			unit.PosX = to.x;
			unit.PosY = to.y;
			Landscape[to.x][to.y].Unit = unit;
			
			if(unit.OrderProgress >= unit.OrderPath.size())
				unit.State = Unit.OUT_OF_ORDERS;
			
			return true;
		}
		return false;
	}
	
	private void TurnMoveUnit(Unit unit, Point to)
	{	
		if(!Landscape[to.x][to.y].Passable)
		{
			unit.State = Unit.OUT_OF_ORDERS;
			return;
		}
		if(TurnPlaceUnit(unit, to))
			return;
		
		Unit otherUnit = Landscape[to.x][to.y].Unit;
		if(unit.Team != otherUnit.Team)
		{
			// Combat!
			if(TurnCombat(unit, otherUnit, to))
			{
				if(otherUnit.HealthPercentage <= 0 || !TurnUnitFlee(otherUnit))
				{
					Landscape[otherUnit.PosX][otherUnit.PosY].Unit = null;
					Units.remove(otherUnit);
					otherUnit.State = Unit.DEAD;
				}
				TurnMoveUnit(unit, to);
				unit.State = Unit.OUT_OF_ORDERS;
			}
			else
			{
				if(unit.HealthPercentage <= 0)
				{
					Units.remove(unit);
					Landscape[unit.PosX][unit.PosY].Unit = null;
					unit.State = Unit.DEAD;
				}
			}
			if(otherUnit.State != Unit.GUARDING && otherUnit.State != Unit.DEAD)
				otherUnit.State = Unit.OUT_OF_ORDERS;
		}
	}

	public void onSurfaceCreated(GL10 gl) {
		Iterator<Unit> it = Units.iterator();
		Unit unit;
		while(it.hasNext())
		{
			unit = it.next();
			unit.onSurfaceCreated(gl);
		}
		for(int x = 0; x < Size; ++x)
		{
			for(int y = 0; y < Size; ++y)
			{
				Landscape[x][y].onSurfaceCreated(gl);
			}
		}
	}
	
	public void PackageUnits(Bundle instanceState, ArrayList<Unit> units)
	{
		int unitAmount = units.size();
		instanceState.putInt("WORLD_UNIT_AMOUNT", unitAmount);
		int state[] = new int[unitAmount];
		int posX[] = new int[unitAmount];
		int posY[] = new int[unitAmount];
		
		Unit unit;
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = units.get(i);
			posX[i] = unit.PosX;
			posY[i] = unit.PosY;
			state[i] = unit.State;
		}
		instanceState.putIntArray("WORLD_UNIT_POSX", posX);
		instanceState.putIntArray("WORLD_UNIT_POSY", posY);
		instanceState.putIntArray("WORLD_UNIT_STATE", state);
		
		SaveUnitOrders(instanceState, units);
	}
	
	public void UnpackageUnits(Bundle instanceState)
	{
		ArrayList<Unit> units = new ArrayList<Unit>();
		
		int unitAmount = instanceState.getInt("WORLD_UNIT_AMOUNT");
		
		int posX[] = instanceState.getIntArray("WORLD_UNIT_POSX");
		int posY[] = instanceState.getIntArray("WORLD_UNIT_POSY");
		int state[] = instanceState.getIntArray("WORLD_UNIT_STATE");
		
		Unit unit;
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = new Unit(this);
			unit.PosX = posX[i];
			unit.PosY = posY[i];
			unit.State = state[i];
			units.add(unit);
		}
		
		LoadUnitOrders(instanceState, units);
		
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = units.get(i);
			Landscape[unit.PosX][unit.PosY].Unit.OrderPath = unit.OrderPath;
			Landscape[unit.PosX][unit.PosY].Unit.State = unit.State;
		}
	}
	
	public void SaveUnitOrders(Bundle instanceState, ArrayList<Unit> units)
	{
		int unitAmount = units.size();
		
		int orderSize[] = new int[unitAmount];
		ArrayList<Integer> orderPosX = new ArrayList<Integer>();
		ArrayList<Integer> orderPosY = new ArrayList<Integer>();

		Unit unit;
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = units.get(i);
			orderSize[i] = unit.OrderPath.size();
			for(int j = 0; j < orderSize[i]; ++j)
			{
				orderPosX.add(unit.OrderPath.get(j).x);
				orderPosY.add(unit.OrderPath.get(j).y);
			}
		}

		instanceState.putIntArray("WORLD_UNIT_ORDERSIZE", orderSize);
		instanceState.putIntegerArrayList("WORLD_UNIT_ORDERPATH_X", orderPosX);
		instanceState.putIntegerArrayList("WORLD_UNIT_ORDERPATH_Y", orderPosY);
	}
	
	public void SaveUnitState(Bundle instanceState, ArrayList<Unit> units)
	{
		int unitAmount = units.size();
		instanceState.putInt("WORLD_UNIT_AMOUNT", unitAmount);
		
		int posX[] = new int[unitAmount];
		int posY[] = new int[unitAmount];
		int type[] = new int[unitAmount];
		int team[] = new int[unitAmount];
		int state[] = new int[unitAmount];
		float health[] = new float[unitAmount];
		
		Unit unit;
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = units.get(i);
			posX[i] = unit.PosX;
			posY[i] = unit.PosY;
			type[i] = unit.Type;
			team[i] = unit.Team;
			state[i] = unit.State;	
			health[i] = unit.HealthPercentage;
		}
		
		instanceState.putIntArray("WORLD_UNIT_POSX", posX);
		instanceState.putIntArray("WORLD_UNIT_POSY", posY);
		instanceState.putIntArray("WORLD_UNIT_TYPE", type);
		instanceState.putIntArray("WORLD_UNIT_TEAM", team);
		instanceState.putIntArray("WORLD_UNIT_STATE", state);
		instanceState.putFloatArray("WORLD_UNIT_HEALTH", health);
		
		SaveUnitOrders(instanceState, units);
	}
	
	public void LoadUnitOrders(Bundle instanceState, ArrayList<Unit> units)
	{
		int unitAmount = instanceState.getInt("WORLD_UNIT_AMOUNT");
		
		int orderSize[] = instanceState.getIntArray("WORLD_UNIT_ORDERSIZE");
		ArrayList<Integer> orderPosX = instanceState.getIntegerArrayList("WORLD_UNIT_ORDERPATH_X");
		ArrayList<Integer> orderPosY = instanceState.getIntegerArrayList("WORLD_UNIT_ORDERPATH_Y");

		Unit unit;
		int orderPos = 0;
		Point p;
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = units.get(i);
			for(int j = 0; j < orderSize[i]; ++j)
			{
				p = new Point();
				p.x = orderPosX.get(orderPos);
				p.y = orderPosY.get(orderPos);
				unit.OrderPath.add(p);
				
				++orderPos;
			}
		}
	}
	
	//TODO: Designation?
	public void LoadUnitState(Bundle instanceState, ArrayList<Unit> units)
	{
		int unitAmount = instanceState.getInt("WORLD_UNIT_AMOUNT");
		
		int posX[] = instanceState.getIntArray("WORLD_UNIT_POSX");
		int posY[] = instanceState.getIntArray("WORLD_UNIT_POSY");
		int type[] = instanceState.getIntArray("WORLD_UNIT_TYPE");
		int team[] = instanceState.getIntArray("WORLD_UNIT_TEAM");
		int state[] = instanceState.getIntArray("WORLD_UNIT_STATE");
		float health[] = instanceState.getFloatArray("WORLD_UNIT_HEALTH");
		
		Unit unit;
		for(int i = 0; i < unitAmount; ++i)
		{
			unit = Unit.CreateUnit(type[i], posX[i], posY[i], team[i], this);
			unit.State = state[i];		
			unit.HealthPercentage = health[i];
			
			units.add(unit);
		}
		
		LoadUnitOrders(instanceState, units);
	}

	public void SaveState(Bundle instanceState) 
	{	
		instanceState.putInt("WORLD_TURNS_LEFT", TurnsLeft);
		instanceState.putString("WORLD_LEVEL_FILE", LevelFile);
		
		SaveUnitState(instanceState, Units);
		
		int goalSize = Goals.size();
		int goalIds[] = new int[goalSize];
		int goalOwners[] = new int[goalSize];
		
		GoalTile goal;
		for(int i = 0; i < goalSize; ++i)
		{
			goal = Goals.get(i);
			goalIds[i] = goal.id;
			goalOwners[i] = goal.Owner;
		}
		instanceState.putIntArray("WORLD_GOAL_ID", goalIds);
		instanceState.putIntArray("WORLD_GOAL_OWNER", goalOwners);		
	}
	
	public void RestoreState(Bundle instanceState)
	{
		LevelFile = instanceState.getString("WORLD_LEVEL_FILE");
		TurnsLeft = instanceState.getInt("WORLD_TURNS_LEFT");
		ParseLevelFile(LevelFile, true);
		
		int goalIds[] = instanceState.getIntArray("WORLD_GOAL_ID");
		int goalOwners[] = instanceState.getIntArray("WORLD_GOAL_OWNER");
		int goalSize = Goals.size();
		GoalTile goal;
		for(int i = 0; i < goalSize; ++i) {
			goal = Goals.get(i);
			for(int j = 0; j < goalSize; ++j) {
				if(goal.id == goalIds[j]) {
					goal.Owner = goalOwners[j];
					break;
				}
			}
		}
		
		ArrayList<Unit> units = new ArrayList<Unit>();
		LoadUnitState(instanceState, units);

		Iterator<Unit> it = units.iterator();
		while(it.hasNext())
			AddUnit(it.next());

		CalculateVisibility();
	}
}
